package com.feng.fengDUtil.dao;

import com.feng.fengDUtil.MyAnnotationUtils;
import com.feng.fengDUtil.MyClassUtils;
import com.feng.fengDUtil.Reflections;
import com.feng.fengDUtil.TransUtil;
import com.feng.fengDUtil.dao.annotation.Clob;
import com.feng.fengDUtil.dao.annotation.Column;
import com.feng.fengDUtil.dao.annotation.ColumnIgnore;
import com.feng.fengDUtil.dao.annotation.Enum;
import com.feng.fengDUtil.dao.annotation.Geometry;
import com.feng.fengDUtil.dao.annotation.Id;
import com.feng.fengDUtil.dao.annotation.Pk;
import com.feng.fengDUtil.dao.annotation.Populate;
import com.feng.fengDUtil.dao.annotation.Table;
import com.feng.fengDUtil.dao.converter.impl.ClobDefaultConverter;
import com.feng.fengDUtil.dao.converter.impl.EnumDefaultConverter;
import com.feng.fengDUtil.dao.exception.IdNotExistException;
import com.feng.fengDUtil.dao.exception.IdPkConflictOrBothNullException;
import com.feng.fengDUtil.dao.exception.PkNotExistException;
import com.feng.fengDUtil.dao.pojo.InsertTypeEnum;
import com.github.mnadeem.TableNameParser;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

public class DbHelp {

  private static final Map<String, String> tableMap = new HashMap();
  private static Map<Class, TableInfo> tableInfoMap = new HashMap();


  public DbHelp() {
  }

  public static String filterSql(String sql) {
    if (!tableMap.isEmpty()) {
      Collection<String> tables = new TableNameParser(sql).tables();
      for (String tableName : tables) {
        sql = sql.replaceAll("(?i)" + tableName, tableMap.get(tableName));
      }
    }
    return sql;
  }

  public static SqlAndParams getInsertSqlAndParams(Object bean, InsertTypeEnum insertType) {
    SqlAndParams sqlAndParams = new SqlAndParams();
    TableInfo tableInfo = getTableInfo(bean.getClass());
    sqlAndParams.setSql(tableInfo.getInsertSql(insertType));
    sqlAndParams.setParams(getInsertParams(tableInfo, bean));
    return sqlAndParams;
  }

  public static SqlAndParams getInsertSqlAndParamsWithoutNil(Object bean,
      InsertTypeEnum insertType) {
    SqlAndParams sqlAndParams = new SqlAndParams();
    Class<? extends Object> beanClass = bean.getClass();
    TableInfo tableInfo = getTableInfo(beanClass);
    List<FieldInfo> columnList = new ArrayList();
    List<Object> paramList = new ArrayList();
    FieldInfo idFieldInfo = tableInfo.getIdFieldInfo();
    if (idFieldInfo != null) {
      if (!idFieldInfo.isIdGenerateByDb()) {
        collectColumnAndParam(bean, columnList, paramList, idFieldInfo);
      }

      collectColumnAndParamList(bean, columnList, paramList, tableInfo.getFieldList());
    } else {
      collectColumnAndParamList(bean, columnList, paramList, tableInfo.getPkFieldList());
      collectColumnAndParamList(bean, columnList, paramList, tableInfo.getFieldWithoutIdPkList());
    }

    String insertSql = SqlUtil.geneInsertSql(tableInfo.getTableName(), columnList);
    sqlAndParams.setSql(new StringBuilder(insertType.getSqlPart()).append(insertSql).toString());
    sqlAndParams.setParams(paramList.toArray());
    return sqlAndParams;
  }

  private static void collectColumnAndParamList(Object bean, List<FieldInfo> columnList,
      List<Object> paramList, List<FieldInfo> fieldInfoList) {
    Iterator<FieldInfo> it = fieldInfoList.iterator();
    while (it.hasNext()) {
      FieldInfo fieldInfo = it.next();
      collectColumnAndParam(bean, columnList, paramList, fieldInfo);
    }
  }

  private static void collectColumnAndParam(Object bean, List<FieldInfo> columnList,
      List<Object> paramList, FieldInfo fieldInfo) {
    Object fieldValue = fieldInfo.getWrapValue(bean);
    if (fieldValue != null) {
      columnList.add(fieldInfo);
      paramList.add(fieldValue);
    }
  }

  public static SqlAndParams getUpdateSqlAndParamsWithoutNil(Object bean) {
    SqlAndParams sqlAndParams = new SqlAndParams();
    Class<? extends Object> beanClass = bean.getClass();
    TableInfo tableInfo = getTableInfo(beanClass);
    List<FieldInfo> columnList = new ArrayList();
    List<Object> paramList = new ArrayList();
    tableInfo.getFieldWithoutIdPkList().forEach(n -> {
      collectColumnAndParam(bean, columnList, paramList, n);
    });

    FieldInfo idFieldInfo = tableInfo.getIdFieldInfo();
    if (idFieldInfo != null) {
      String updateSql = SqlUtil
          .geneUpdateSql(tableInfo.getTableName(), idFieldInfo.getColumnName(), columnList);
      sqlAndParams.setSql(updateSql);
      Object idValue = Reflections.getFieldValue(bean, idFieldInfo.getField());
      paramList.add(idValue);
      sqlAndParams.setParams(paramList.toArray());
      return sqlAndParams;
    } else {
      List<FieldInfo> pkFieldList = tableInfo.getPkFieldList();
      if (pkFieldList == null) {
        throw new IdPkConflictOrBothNullException();
      } else {
        List<FieldInfo> pkColumnNameList = new ArrayList();
        pkFieldList.forEach(fieldInfo -> {
          pkColumnNameList.add(fieldInfo);
          paramList.add(Reflections.getFieldValue(bean, fieldInfo.getField()));
        });
        String updateSql = SqlUtil
            .geneUpdateSqlPk(tableInfo.getTableName(), pkColumnNameList, columnList);
        sqlAndParams.setSql(updateSql);
        sqlAndParams.setParams(paramList.toArray());
        return sqlAndParams;
      }
    }
  }

  public static SqlAndParams getDeleteByIdSqlAndParams(Class clazz, Object id) {
    TableInfo tableInfo = getTableInfo(clazz);
    if (tableInfo.getIdFieldInfo() == null) {
      throw new IdNotExistException();
    } else {
      SqlAndParams sqlAndParams = new SqlAndParams();
      String tableName = tableInfo.getTableName();
      String idName = tableInfo.getIdFieldInfo().getColumnName();
      StringBuilder deleteByIdSql = new StringBuilder("delete from ");
      deleteByIdSql.append(tableName).append(" where ").append(idName).append(" = ? ");
      sqlAndParams.setSql(deleteByIdSql.toString());
      sqlAndParams.setParams(new Object[]{id});
      return sqlAndParams;
    }
  }

  public static SqlAndParams getDeleteByPkSqlAndParams(Class clazz, Object[] pks) {
    TableInfo tableInfo = getTableInfo(clazz);
    List<FieldInfo> pkFieldList = tableInfo.getPkFieldList();
    if (pkFieldList == null) {
      throw new PkNotExistException();
    } else if (pks.length != pkFieldList.size()) {
      throw new RuntimeException(
          "参数的个数和联合主键的个数不一致,主键的个数为:" + pkFieldList.size() + ",参数的个数为:" + pks.length
              + ",此外，参数的顺序应和主键在类中声明的顺序一致");
    } else {
      SqlAndParams sqlAndParams = new SqlAndParams();
      String tableName = tableInfo.getTableName();
      StringBuilder deleteByIdSql = new StringBuilder("delete from ");
      deleteByIdSql.append(tableName).append(" where ");
      int size = pkFieldList.size();

      for (int i = 0; i < size; ++i) {
        deleteByIdSql.append((pkFieldList.get(i)).getColumnName()).append(" = ? ");
        if (i + 1 != size) {
          deleteByIdSql.append(" and ");
        }
      }

      sqlAndParams.setSql(deleteByIdSql.toString());
      sqlAndParams.setParams(pks);
      return sqlAndParams;
    }
  }

  public static SqlAndParams getDeleteSql(Object bean) {
    SqlAndParams sqlAndParams = new SqlAndParams();
    Class<? extends Object> beanClass = bean.getClass();
    TableInfo tableInfo = getTableInfo(beanClass);
    List<FieldInfo> columnList = new ArrayList();
    List<Object> paramList = new ArrayList();
    FieldInfo idFieldInfo = tableInfo.getIdFieldInfo();
    Object idValue = idFieldInfo.getWrapValue(bean);
    if (idFieldInfo != null && idValue != null) {
      columnList.add(idFieldInfo);
      paramList.add(idValue);
    }

    tableInfo.getPkFieldList().forEach(fieldInfo -> {
      Object value = idFieldInfo.getWrapValue(bean);
      if (value != null) {
        columnList.add(fieldInfo);
        paramList.add(value);
      }
    });

    tableInfo.getFieldWithoutIdPkList().forEach(fieldInfo -> {
      collectColumnAndParam(bean, columnList, paramList, fieldInfo);
    });

    if (columnList.size() <= 0) {
      throw new RuntimeException("整个对象的字段都为null,应至少保证有一个字段的字为非空，否则调用该api没有意义");
    } else {
      String deleteSql = SqlUtil.geneDelSqlByWhere(tableInfo.getTableName(), columnList);
      sqlAndParams.setSql(deleteSql);
      sqlAndParams.setParams(paramList.toArray());
      return sqlAndParams;
    }
  }

  public static SqlAndParams getDeleteByWhereSql(Class tableClass, String whereSql,
      Object[] params) {
    SqlAndParams sqlAndParams = new SqlAndParams();
    TableInfo tableInfo = getTableInfo(tableClass);
    StringBuilder sb = new StringBuilder(100);
    sb.append("delete from ").append(tableInfo.getTableName()).append(" where ").append(whereSql);
    String deleteSql = sb.toString();
    sqlAndParams.setSql(deleteSql);
    sqlAndParams.setParams(params);
    return sqlAndParams;
  }

  public static SqlAndParams getUpdateSqlAndParams(Object bean) {
    SqlAndParams sqlAndParams = new SqlAndParams();
    TableInfo tableInfo = getTableInfo(bean.getClass());
    sqlAndParams.setSql(tableInfo.getUpdateSql());
    sqlAndParams.setParams(getUpdateParams(tableInfo, bean));
    return sqlAndParams;
  }

  public static <T> SqlAndParams getUpdateAllSqlAndParams(List<T> beans) {
    SqlAndParams sqlAndParams = new SqlAndParams();
    TableInfo tableInfo = getTableInfo(beans.get(0).getClass());
    String batchUpdateSqlInner = tableInfo.getBatchUpdateSqlInner();
    String innerSql = beans.stream().map(n -> batchUpdateSqlInner).collect(Collectors.joining(" union "));
    sqlAndParams.setSql(String.format(tableInfo.getBatchUpdateSqlOuter(), innerSql));
    sqlAndParams.setParams(getUpdateAllParams(tableInfo, beans));
    return sqlAndParams;
  }


  public static SqlAndParams getSelectByIdSqlAndParams(Class beanClass, Object id) {
    TableInfo tableInfo = getTableInfo(beanClass);
    FieldInfo idFieldInfo = tableInfo.getIdFieldInfo();
    if (idFieldInfo == null) {
      throw new IdNotExistException();
    } else {
      SqlAndParams sqlAndParams = new SqlAndParams();
      sqlAndParams.setSql(tableInfo.getSelectByIdSql());
      sqlAndParams.setParams(new Object[]{id});
      return sqlAndParams;
    }
  }

  public static SqlAndParams getSelectAllSqlAndParams(Class beanClass) {
    TableInfo tableInfo = getTableInfo(beanClass);
    SqlAndParams sqlAndParams = new SqlAndParams();
    sqlAndParams.setSql(tableInfo.getBaseSelectSql());
    return sqlAndParams;
  }

  public static SqlAndParams getSelectAndParams(Class beanClass, String where, Object[] params) {
    TableInfo tableInfo = getTableInfo(beanClass);
    SqlAndParams sqlAndParams = new SqlAndParams();
    sqlAndParams.setSql(
        new StringBuilder(tableInfo.getBaseSelectSql()).append(" where ").append(where).toString());
    sqlAndParams.setParams(params);
    return sqlAndParams;
  }

  public static SqlAndParams getSelectByPkSqlAndParams(Class beanClass, Object[] pks) {
    TableInfo tableInfo = getTableInfo(beanClass);
    List<FieldInfo> pkFieldList = tableInfo.getPkFieldList();
    if (pkFieldList == null) {
      throw new PkNotExistException();
    } else if (pks.length != pkFieldList.size()) {
      throw new RuntimeException(
          "参数的个数和联合主键的个数不一致,主键的个数为:" + pkFieldList.size() + ",参数的个数为:" + pks.length
              + ",此外，参数的顺序应和主键在类中声明的顺序一致");
    } else {
      SqlAndParams sqlAndParams = new SqlAndParams();
      sqlAndParams.setSql(tableInfo.getSelectByPkSql());
      sqlAndParams.setParams(pks);
      return sqlAndParams;
    }
  }

  public static Object[] getInsertParams(TableInfo tableInfo, Object bean) {
    FieldInfo idFieldInfo = tableInfo.getIdFieldInfo();
    List<Object> params = new ArrayList();
    if (idFieldInfo != null) {
      Field idField = idFieldInfo.getField();
      Object idValue = Reflections.getFieldValue(bean, idField);
      params.add(idValue);
    }
    Iterator it = tableInfo.getFieldList().iterator();
    while (it.hasNext()) {
      FieldInfo fieldInfo = (FieldInfo) it.next();
      params.add(fieldInfo.getWrapValue(bean));
    }
    return params.toArray();
  }

  private static Object[] getUpdateParams(TableInfo tableInfo, Object bean) {
    FieldInfo idFieldInfo = tableInfo.getIdFieldInfo();
    if (idFieldInfo != null) {
      List<Object> params = new ArrayList();
      tableInfo.getFieldList().forEach(fieldInfo -> {
        params.add(fieldInfo.getWrapValue(bean));
      });

      Object idValue = Reflections.getFieldValue(bean, idFieldInfo.getField());
      params.add(idValue);
      return params.toArray();
    } else {
      List<FieldInfo> pkFieldList = tableInfo.getPkFieldList();
      if (pkFieldList == null) {
        throw new IdPkConflictOrBothNullException();
      } else {
        List<Object> params = new ArrayList();
        tableInfo.getFieldWithoutIdPkList().forEach(fieldInfo -> {
          params.add(fieldInfo.getWrapValue(bean));
        });
        pkFieldList.forEach(fieldInfo -> {
          params.add(Reflections.getFieldValue(bean, fieldInfo.getField()));
        });
        return params.toArray();
      }
    }
  }

  private static <T> Object[] getUpdateAllParams(TableInfo tableInfo, List<T> beans) {
    List<FieldInfo> allFields = tableInfo.getAllFields();
    List<Object> params = new ArrayList<>();
    beans.forEach(bean ->
        params.addAll(TransUtil.mapToList(allFields, n -> n.getWrapValue(bean)))
    );
    return params.toArray();
  }


  public static TableInfo getTableInfo(Class clazz) {
    TableInfo tableInfoCache = tableInfoMap.get(clazz);
    if (tableInfoCache != null) {
      return tableInfoCache;
    } else {
      TableInfo tableInfo = new TableInfo();
      initTableInfo(clazz, tableInfo);
      tableInfoMap.put(clazz, tableInfo);
      return tableInfo;
    }
  }

  public static String getTableName(Class clazz) {
    Table findAnnotation = MyAnnotationUtils.findAnnotation(clazz, Table.class);
    String tableName =
        findAnnotation == null ? MyClassUtils.getShortName(clazz) : findAnnotation.value();
    return tableMap.getOrDefault(tableName, tableName);
  }

  private static boolean isIgnore(Field field) {
    if (Modifier.isStatic(field.getModifiers())) {
      return true;
    } else {
      ColumnIgnore columnIgnore = field.getAnnotation(ColumnIgnore.class);
      Populate populate = field.getAnnotation(Populate.class);
      return columnIgnore != null || populate != null;
    }
  }

  private static void initTableInfo(Class clazz, TableInfo tableInfo) {
    List<FieldInfo> fieldInfoList = new ArrayList();
    Field[] arr = FieldUtils.getAllFields(clazz);
    for (int i = 0, len = arr.length; i < len; ++i) {
      Field field = arr[i];
      if (field.getAnnotation(Id.class) != null) {
        processId(tableInfo, field);
      } else if (field.getAnnotation(Pk.class) != null) {
        processPk(tableInfo, fieldInfoList, field);
      } else if (field.getAnnotation(Populate.class) != null) {
        processPopulate(tableInfo, field);
      } else if (isIgnore(field)) {

      } else {
        processNormal(tableInfo, fieldInfoList, field);
      }
    }
    tableInfo.setFieldList(fieldInfoList);
    tableInfo.setTableName(getTableName(clazz));
    tableInfo.generateSql();
  }

  private static void processPopulate(TableInfo tableInfo, Field field) {

    PopulateInfo populateInfo = new PopulateInfo();
    populateInfo.setField(field);
    Populate populate = field.getAnnotation(Populate.class);
    populateInfo.setForeignKey(populate.value());
    Type genericType = field.getGenericType();
    if (genericType instanceof ParameterizedType) {
      ParameterizedType pt = (ParameterizedType) genericType;
      populateInfo.setBeanClass((Class) pt.getActualTypeArguments()[0]);
    }
    tableInfo.addPopulate(populateInfo);
  }

  private static void processPk(TableInfo tableInfo, List<FieldInfo> fieldInfoList, Field field) {
    if (tableInfo.getIdFieldInfo() != null) {
      throw new IdPkConflictOrBothNullException();
    } else {
      List<FieldInfo> pkFieldList = tableInfo.getPkFieldList();
      if (pkFieldList == null) {
        pkFieldList = new ArrayList();
        tableInfo.setPkFieldList(pkFieldList);
      }

      String columnValue = getColumnValue(field);
      FieldInfo fieldInfo = FieldInfo.buildFieldInfo(false, field, columnValue, false);
      pkFieldList.add(fieldInfo);
      fieldInfoList.add(fieldInfo);
    }
  }

  private static void processNormal(TableInfo tableInfo, List<FieldInfo> fieldInfoList,
      Field field) {
    String columnValue = getColumnValue(field);
    Geometry geometry = field.getAnnotation(Geometry.class);

    FieldInfo fieldInfo = FieldInfo.buildFieldInfo(false, field, columnValue, false, geometry);
    Clob clob = field.getAnnotation(Clob.class);
    if (clob != null) {
      fieldInfo.setConverter(new ClobDefaultConverter());
    }
    Enum en = field.getAnnotation(Enum.class);
    if (en != null) {
      fieldInfo.setConverter(new EnumDefaultConverter());
    }
    fieldInfoList.add(fieldInfo);
    tableInfo.getFieldWithoutIdPkList().add(fieldInfo);
  }

  private static void processId(TableInfo tableInfo, Field field) {
    if (tableInfo.getIdFieldInfo() == null) {
      Id idAnnotaion = field.getAnnotation(Id.class);
      boolean idGenerateByDb = idAnnotaion.value().equals(Id.IdType.autoIncre);
      String columnValue = getColumnValue(field);
      FieldInfo idFieldInfo = FieldInfo.buildFieldInfo(true, field, columnValue, idGenerateByDb);
      tableInfo.setIdFieldInfo(idFieldInfo);
    } else {
      throw new IdPkConflictOrBothNullException();
    }
  }

  private static String getColumnValue(Field field) {
    Column column = field.getAnnotation(Column.class);
    if (column == null) {
      return field.getName();
    } else {
      String value = column.value();
      return StringUtils.isEmpty(value) ? field.getName() : column.value();
    }
  }

  public static void main(String[] args) {
  }

  public static Map<String, String> getTableMap() {
    return tableMap;
  }


}
